package com.idanchuang.component.core.helper;


import com.alibaba.ttl.TransmittableThreadLocal;
import com.idanchuang.component.core.Dc;
import com.idanchuang.component.core.config.EnvConfigBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * @author yjy
 * @date 2020/8/31 12:12
 **/
public class EnvContext {

    /** 配置信息 */
    private static EnvConfigBean envConfigBean;

    /** 透传变量的requestHeaderName */
    public static final String TRANSMIT_ATTRIBUTE_HEADER_NAME = "DC-ENV-ATTRIBUTES";

    private static final String KEY_ENV = "keyEnv";
    private static final String KEY_DC_TRACE_ID = "keyDcTraceId";

    private static final ThreadLocal<String> CURRENT_PATH = new TransmittableThreadLocal<>();

    /** 自定义 环境变量 */
    private static final ThreadLocal<HashMap<String, Object>> ATTRIBUTE_MAP = new TransmittableThreadLocal<>();

    /** 设置配置信息 (init in EnvConfigBean.afterPropertiesSet) */
    public static void setEnvConfigBean(EnvConfigBean envConfigBean) {
        EnvContext.envConfigBean = envConfigBean;
    }

    /**
     * 设置当前请求path
     * @param path ..
     */
    public static void setCurrentPath(String path) {
        CURRENT_PATH.set(path);
    }

    /**
     * 获取当前请求path
     * @return ..
     */
    public static String getCurrentPath() {
        return CURRENT_PATH.get();
    }

    /**
     * 清除当前请求path
     */
    public static void removeCurrentPath() {
        CURRENT_PATH.remove();
    }

    /**
     * 设置当前环境标识
     * @param env 环境标识
     */
    public static void setCurrentEnv(String env) {
        setAttribute(KEY_ENV, env);
    }

    /**
     * @return 当前的压测标识
     */
    public static String getCurrentEnv() {
        return getAttribute(KEY_ENV);
    }

    /**
     * 移除当前压测标识
     */
    public static void removeCurrentEnv() {
        removeAttribute(KEY_ENV);
    }

    /**
     * @return 当前登入用户的id (兼容APP用户与SSO用户)
     */
    public static String getUserId() {
        return getAttribute(Dc.USER_ID);
    }

    /**
     * @return 当前登入的用户idCode
     */
    public static String getUserIdCode() {
        return getAttribute(Dc.USER_ID_CODE);
    }

    /**
     * @return 当前是否为诊断请求
     */
    public static boolean isDiagnoses() {
        String diag = getAttribute(Dc.DIAG);
        return diag != null && !diag.isEmpty();
    }

    /**
     * @return 当前是否为压测环境
     */
    public static boolean isPressure() {
        if (envConfigBean == null || envConfigBean.isDisabled()) {
            return false;
        }
        String current = getCurrentEnv();
        return current != null && current.equalsIgnoreCase(envConfigBean.getPressureValue());
    }

    public static boolean isPressureForMysql() {
        return isPressure() && !envConfigBean.isDisabledMysql();
    }

    public static boolean isPressureForTable(String table) {
        if (!isPressureForMysql()) {
            return false;
        }
        // 跳过白名单表
        List<String> ignoreTables = envConfigBean.getIgnoreTableList();
        if (!ignoreTables.isEmpty() && ignoreTables.contains(table)) {
            return false;
        }
        // 如果指定了压测表, 那么仅对指定的表走影子表
        List<String> tables = envConfigBean.getPressureTableList();
        return tables.isEmpty() || tables.contains(table);
    }

    public static boolean isPressureForRedis() {
        return isPressure() && !envConfigBean.isDisabledRedis();
    }

    public static boolean isPressureForLog() {
        return isPressure() && !envConfigBean.isDisabledLog();
    }

    public static boolean isPressureForAnnotation() {
        return isPressure() && !envConfigBean.isDisabledAnnotation();
    }

    /**
     * @return 环境标头
     */
    public static String getEnvHeaderName() {
        if (envConfigBean == null) {
            return "DC-ENV";
        }
        return envConfigBean.getHeaderName();
    }

    /**
     * @return 前端traceId头
     */
    public static String getDcTraceIdHeaderName() {
        if (envConfigBean == null) {
            return "DC-TRACE-ID";
        }
        return envConfigBean.getDcTraceIdHeaderName();
    }

    /** 设置当前traceId */
    public static void setDcTraceIdIfAbsent() {
        setDcTraceIdIfAbsent(createDcTraceId());
    }

    /** 设置当前traceId */
    public static void setDcTraceIdIfAbsent(String traceId) {
        setAttributeIfAbsent(KEY_DC_TRACE_ID, traceId);
    }

    /** 设置当前traceId */
    public static void setDcTraceId(String traceId) {
        setAttribute(KEY_DC_TRACE_ID, traceId);
    }

    /**
     * @return 获取当前traceId
     */
    public static String getDcTraceId() {
        return getAttribute(KEY_DC_TRACE_ID);
    }

    /**
     * 移除当前traceId
     */
    public static void removeDcTraceId() {
        removeAttribute(KEY_DC_TRACE_ID);
    }

    public static EnvConfigBean getEnvConfigBean() {
        return envConfigBean;
    }

    /**
     * @return 生成一个自定义的链路id
     */
    public static String createDcTraceId() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 清理所有环境变量
     */
    public static void clearContext() {
        updateAttributes(null);
        EnvContext.removeCurrentPath();
    }

    /**
     * 在当前链路中为某一个服务指定一个特定的实例 (意味着这个链路下,访问这个服务的时候就直接访问这个实例)
     * @param serviceId 服务名
     * @param instance 实例信息 (ip:port , 如: 127.0.0.1:8080)
     */
    public static void addSpecialInstance(String serviceId, String instance) {
        Map<String, String> specials = getAttribute(Dc.SPECIAL_INSTANCE);
        if (specials == null) {
            specials = new HashMap<>();
            setAttribute(Dc.SPECIAL_INSTANCE, specials);
        }
        specials.put(serviceId.toLowerCase(), instance);
    }

    /**
     * 获取当前链路指定的服务实例
     * @param serviceId 服务名
     * @return 特定实例
     */
    @Nullable
    public static String getSpecialInstance(String serviceId) {
        Map<String, String> specials = getAttribute(Dc.SPECIAL_INSTANCE);
        if (specials == null) {
            return null;
        }
        return specials.get(serviceId);
    }

    /**
     * 获取环境变量
     * @param key 变量key
     * @param <T> 值类型
     * @return 值
     */
    public static <T> T getAttribute(String key) {
        Assert.notNull(key, "key can not be null");
        Map<String, Object> dataMap = getAttributes();
        return (T)dataMap.get(key);
    }

    /**
     * 是否存在环境变量
     * @param key 变量key
     * @return 是否存在
     */
    public static boolean existAttribute(String key) {
        Assert.notNull(key, "key can not be null");
        Map<String, Object> dataMap = getAttributes();
        return dataMap.containsKey(key);
    }

    /**
     * 设置一个环境变量, 不存在则设置, 存在则不作操作
     * @param key 变量key
     * @param value 变量值
     */
    public static void setAttributeIfAbsent(String key, Object value) {
        Assert.notNull(key, "key can not be null");
        Map<String, Object> dataMap = getAttributes();
        dataMap.putIfAbsent(key, value);
        updateAttributes(dataMap);
    }

    /**
     * 重置环境变量
     * @param attributes 变量集合
     */
    public static void resetAttribute(Map<String, Object> attributes) {
        if (attributes == null) {
            attributes = new HashMap<>();
        }
        updateAttributes(attributes);
    }

    /**
     * 设置环境变量
     * @param attributes 变量集合
     */
    public static void setAllAttribute(Map<String, Object> attributes) {
        if (attributes == null || attributes.isEmpty()) {
            return;
        }
        Map<String, Object> dataMap = getAttributes();
        dataMap.putAll(attributes);
        updateAttributes(dataMap);
    }

    /**
     * 设置一个环境变量, 如果已存在则覆盖
     * @param key 变量key
     * @param value 变量值
     */
    public static void setAttribute(String key, Object value) {
        Assert.notNull(key, "key can not be null");
        Map<String, Object> dataMap = getAttributes();
        dataMap.put(key, value);
        updateAttributes(dataMap);
    }

    /**
     * 移除一个环境变量
     * @param key 变量key
     * @param <T> 值类型
     * @return 被移除的值
     */
    public static <T> T removeAttribute(String key) {
        Assert.notNull(key, "key can not be null");
        Map<String, Object> dataMap = getAttributes();
        if (dataMap.containsKey(key)) {
            Object data = dataMap.remove(key);
            updateAttributes(dataMap);
            return (T)data;
        }
        return null;
    }

    /**
     * 获取当前环境变量数量
     * @return 数量
     */
    public static int getAttributeCount() {
        return getAttributes().size();
    }

    /**
     * 获取所有环境变量
     * @return ..
     */
    public static Map<String, Object> getAttributes() {
        HashMap<String, Object> dataMap = ATTRIBUTE_MAP.get();
        if (dataMap == null) {
            return new HashMap<>();
        }
        return dataMap;
    }

    /**
     * 更新环境变量
     * @param dataMap 变量集合
     */
    private synchronized static void updateAttributes(Map<String, Object> dataMap) {
        if (dataMap == null) {
            ATTRIBUTE_MAP.remove();
            return;
        }
        if (dataMap instanceof HashMap) {
            ATTRIBUTE_MAP.set((HashMap)dataMap);
            return ;
        }
        ATTRIBUTE_MAP.set(new HashMap<>(dataMap));
    }

}
